
package com.mephi.io;


import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import javax.swing.ProgressMonitorInputStream;
import javax.swing.SwingWorker;
import org.apache.log4j.Logger;
import com.mephi.controller.GameImpl;
import com.mephi.controller.GameResult;
import com.mephi.exception.InvalidGameFormatException;
import com.mephi.interfaces.Game;
import com.mephi.interfaces.ImportExportGame;
import com.mephi.model.Move;
import com.mephi.util.Constants;
import com.mephi.util.Ext;
import com.mephi.util.PieceColor;
import com.mephi.util.PieceLoaderKey;


/**
 * Simple default implementation import/export operations.
 * @author mephisto
 * 2011-06-04
 */
public class ImportExportGameImpl implements ImportExportGame<Move>
{
	/**
	 * 
	 * @param game game which will be exported
	 * @param path the path to which it will be exported
	 * @param ext the extension of file to set
	 * 
	 * @throws NullPointerException if game or path is/are null
	 */
	@Override
	public void exportGame(Game<Move> game, String path, Ext ext) 
		throws FileNotFoundException
	{
		if (game == null)
			throw new NullPointerException("game is null!");
		if (path == null)
			throw new NullPointerException("path is null!");
		
		PrintWriter out = new PrintWriter(path);
		StringBuilder sB = new StringBuilder();
		
		Date currentDate = ((GregorianCalendar)game.getCalendar()).getTime();
        Date currentTime = currentDate;
        DateFormat currentDateFormat;
        DateFormat currentTimeFormat;
        
        Locale currentLocale = Locale.GERMAN;
        currentDateFormat = new SimpleDateFormat("yyyy.MM.dd");
        String date = currentDateFormat.format(currentDate);

        int timeStyle = DateFormat.MEDIUM;
        currentTimeFormat = DateFormat.getTimeInstance(timeStyle, currentLocale);
        String time = currentTimeFormat.format(currentTime);
		
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("Event").append(Constants.SPACE_STRING)
				.append(Constants.QUOTATION_MARK).append(game.getEvent()).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());
		
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("Site").append(Constants.SPACE_STRING)
				.append(Constants.QUOTATION_MARK).append(game.getSite()).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());
		//poprawic
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("Date").append(Constants.SPACE_STRING)
				.append(Constants.QUOTATION_MARK).append(date).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());
		
		String roundStr = Constants.PAUSE_STRING;
		if(game.getRound() > 0)
			roundStr = Integer.toString(game.getRound());
		
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("Round").append(Constants.SPACE_STRING)
				.append(Constants.QUOTATION_MARK).append(roundStr).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());
		
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("White").append(Constants.SPACE_STRING)
				.append(Constants.QUOTATION_MARK).append(game.getWhite()).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());
		
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("Black").append(Constants.SPACE_STRING)
				.append(Constants.QUOTATION_MARK).append(game.getBlack()).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());		
		
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("Result").append(Constants.SPACE_STRING)
				.append(Constants.QUOTATION_MARK).append(game.getResult()).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());	
		//Time
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("Time").append(Constants.SPACE_STRING)
				.append(Constants.QUOTATION_MARK).append(time).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());	
		
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("TimeControl").append(Constants.SPACE_STRING)
				.append(Constants.QUOTATION_MARK).append(game.getTimeControl()).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());
		
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("WhiteElo").append(Constants.SPACE_STRING)
				.append(Constants.QUOTATION_MARK).append(game.getWhiteElo()).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());	
		
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("BlackElo").append(Constants.SPACE_STRING)
				.append(Constants.QUOTATION_MARK).append(game.getBlackElo()).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());	

		//pieces position
		sB.append(Constants.LEFT_SQUARE_BRACKET_MARK).append("Position: white: \"").append(
				getPiecesPosition(game, PieceColor.WHITE)).append(Constants.QUOTATION_MARK).append(" black: \"")
				.append(getPiecesPosition(game, PieceColor.BLACK)).append(Constants.QUOTATION_MARK)
				.append(Constants.RIGHT_SQUARE_BRACKET_MARK);
		out.println(sB);
		sB.delete(0, sB.length());
		
		out.println(Constants.EMPTY_STRING);
		
		
		List<Move> moves = game.getMovesList();
		for(int i = 0, size = moves.size(); i < size; ++i)
		{
			sB.append(Integer.toString(i+1)).append(Constants.DOT_STRING).append(Constants.SPACE_STRING)
				.append(moves.get(i).getWhiteMove()).append(Constants.SPACE_STRING).append(moves.get(i).getBlackMove())
				.append(Constants.SPACE_STRING);
			
			if (sB.length() >= 70)
			{
				out.println(sB);
				sB.delete(0, sB.length());	
			}
		}
		out.println(sB);
		
		out.close();
	}

	/**
	 * Implementation reading from a file saved in txt format
	 * 
	 * @param path the path from which game will be imported to set
	 * @return the imported game
	 * 
	 * @throws FileNotFoundException if file won't be found
	 * @throws IOException if some errors occur during I/O operations
	 * @throws InvalidGameFormatException if game format is invalid
	 * @throws IllegalArgumentException if path is null
	 */
	@Override
	public Game<Move> importGame(String path) 
		throws FileNotFoundException, IOException, InvalidGameFormatException
	{
		if (path == null)
			throw new IllegalArgumentException("path is null!");
		
		int dotPos = path.lastIndexOf(Constants.DOT_STRING);
		String extension = path.substring(dotPos+1);
		Game<Move> game = null;
		if (isExtSupported(extension))
		{
			logger.info("Extension: " + extension + " is supported");
		    SwingWorker<Game<Move>, String> worker = new SwingWorkerImporter(path);
	        worker.execute();
		         
	        try
			{
				game = worker.get();
			}
			catch (InterruptedException e)
			{
				logger.debug(e);
			}
			catch (ExecutionException e)
			{
				logger.debug(e);
			}	
		}
		return game;
	}
	
	private StringBuilder getPiecesPosition(Game<Move> game, PieceColor pieceColor)
	{
		Map<String, PieceLoaderKey> piecesMap = null;
		StringBuilder sB = new StringBuilder();
		if (pieceColor.equals(PieceColor.WHITE))
			piecesMap = game.getPosition().getWhitePosition();
		else
			piecesMap = game.getPosition().getBlackPosition();
		
		Set<String> set = piecesMap.keySet();	
		for (String key : set)
		{
			PieceLoaderKey pcsKey = piecesMap.get(key);			
			switch (pcsKey)
			{
			case wK:
			case bK:
				sB.append(Constants.K_CAPITAL_LETTER).append(key).append(Constants.COMMA_STRING);
				break;
			case wQ:
			case bQ:
				sB.append(Constants.Q_CAPITAL_LETTER).append(key).append(Constants.COMMA_STRING);
				break;
			case wR:
			case bR:
				sB.append(Constants.R_CAPITAL_LETTER).append(key).append(Constants.COMMA_STRING);
				break;
			case wB:
			case bB:
				sB.append(Constants.B_CAPITAL_LETTER).append(key).append(Constants.COMMA_STRING);	
				break;
			case wN:
			case bN:
				sB.append(Constants.N_CAPITAL_LETTER).append(key).append(Constants.COMMA_STRING);	
				break;
			case wp:
			case bp:
				sB.append(key).append(Constants.COMMA_STRING);
				break;
			}
		}
		if (sB.lastIndexOf(Constants.COMMA_STRING) == (sB.length()-1) && sB.length() > 0)
			sB.deleteCharAt(sB.length()-1);
		logger.debug("position=" + sB);
		return sB;
	}
	
	private boolean isExtSupported(String ext)
	{   
		for (Ext it : supportedExtentions)
			if (it.toString().equals(ext))
				return true;
		return false;
	}
	
	private static final List<Ext> supportedExtentions = new ArrayList<Ext>(); 
	static
	{
		supportedExtentions.add(Ext.txt);
	}
	
	private Logger logger = Logger.getLogger("LOG");
	
	
	final class SwingWorkerImporter extends SwingWorker<Game<Move>, String>
	{   
		public SwingWorkerImporter(String path)
		{
			this.path = path;
		}

		@Override
        protected Game<Move> doInBackground() throws Exception
        {
			File f = new File(path);
			FileInputStream fileIn = new FileInputStream(f);
		    ProgressMonitorInputStream progressIn = new ProgressMonitorInputStream(null, "Reading "
		            + f.getName(), fileIn);
		    Scanner in = new Scanner(progressIn);
			
			Map<String, String> gameHead = new HashMap<String, String>(11);
			StringBuilder strB = new StringBuilder(500);  
            loadFileToBuffer(in, gameHead, strB); 
            
   		    Scanner sc = new Scanner(strB.toString()).useDelimiter(Constants.SPACE_STRING);
   			//size game with 40 moves
   			List<String> tmpList = new ArrayList<String>(122);
   			while (sc.hasNext())
   			{
   				tmpList.add(sc.next());
   	        }
   			Game<Move> game = new GameImpl<Move>(tmpList.size()/3);
   			setGameHeader(game, gameHead); 
   			loadGameMoves(game, tmpList);
   			logger.debug(game);		
   			sc.close(); 
   			publish(game.getWhite()+" vs. " + game.getBlack());

            return game;
        }
		
		// In the EDT
		@Override
		protected void done() 
		{
		    /*try 
		    {
		      for (Image image : get()) 
		      {
		        viewer.add(new JLabel(new ImageIcon(image)));
		        viewer.revalidate();
		      }
		    } catch (Exception e) { }*/
		}

		// In the EDT
		//@Override
		protected void process(String... messages) 
		{
			for (String message : messages) 
			{
				
			    logger.debug(message);
			  	logger.debug("processing...");
			}	  
		}
		  
		private void loadFileToBuffer(Scanner in, Map<String, String> gameHead, StringBuilder strB) 
				throws InvalidGameFormatException
		{   
			String line = null;
			boolean flag = false;
				
	        while (in.hasNextLine())
	        {   
	        	logger.debug("doInBackground: " + line); 
	            line = in.nextLine();
	            if (!flag)
	            {   
	            	if (line.equals(Constants.EMPTY_STRING))
	            		flag = true;
	  				else
	  				{   
	  					line = line.replaceAll("[\\[\"\\]]", Constants.EMPTY_STRING);
	  					String[] strTab = line.split(Constants.SPACE_STRING);
	  					if (strTab.length != 2 && strTab.length != 5)
	  						throw new InvalidGameFormatException(
	  								"Invalid game format causes errors during import! Wrong header!");
	  					if (strTab.length == 2)
	  					{
	  						logger.debug("key: " + strTab[0] + ", value: " +  strTab[1]);
		  					gameHead.put(strTab[0],  strTab[1]);
	  					}
	  					else //strTab.length == 5
	  					{
		  					gameHead.put(strTab[1],  strTab[2]);
		  					logger.debug("key: " + strTab[1] + ", value: " +  strTab[2]);
		  					gameHead.put(strTab[3],  strTab[4]);
		  					logger.debug("key: " + strTab[3] + ", value: " +  strTab[4]);
	  					}
	  					
	  				}
	  			}
	  			else
	  			{
	  				strB.append(line);
	  				strB.append(" ");
	  			}
	         }
	         in.close();
	         logger.debug("Moves: " + strB);
		}
		
		private void setGameHeader(Game<Move> game, Map<String, String> gameHead) 
				throws InvalidGameFormatException
		{
			game.setEvent(gameHead.get("Event"));
   			game.setSite(gameHead.get("Site"));
   			if (!gameHead.get("Round").equals(Constants.PAUSE_STRING))
   				game.setRound(Integer.parseInt(gameHead.get("Round")));
   			game.setWhite(gameHead.get("White"));
   			game.setBlack(gameHead.get("Black"));
   			game.setResult(GameResult.newInstance(gameHead.get("Result")));
   			game.setTimeControl(gameHead.get("TimeControl"));
   			game.setWhiteElo(Integer.parseInt(gameHead.get("WhiteElo")));
   			game.setBlackElo(Integer.parseInt(gameHead.get("BlackElo")));
   			
   			String[] strDate = gameHead.get("Date").split("\\.");
   			//System.out.println("len: "+strDate.length );

   			if (strDate.length != 3)
   				throw new InvalidGameFormatException(
   						"Invalid game format causes errors during import! Wrong date!");
   			String[] strTime = gameHead.get("Time").split(Constants.COLON_STRING);
   			//System.out.println("len: "+strTime.length );
   			for(String it: strTime)
   				System.out.println(it);
   			if (strTime.length != 3)
   				throw new InvalidGameFormatException(
   						"Invalid game format causes errors during import! Wrong time!");
   			Calendar calendar = new GregorianCalendar(
   					Integer.parseInt(strDate[0]),
   					Integer.parseInt(strDate[1])-1,
   					Integer.parseInt(strDate[2]),
   					Integer.parseInt(strTime[0]),
   					Integer.parseInt(strTime[1]),
   					Integer.parseInt(strTime[2])
   					);
   			game.setCalendar(calendar);
   			
   			//loading position
   			
		}
		
		private void loadGameMoves(Game<Move> game, List<String> tmpList)
		{
			String whiteMove=null;
   			String blackMove=null;

   			int counter = 0;
   			for (String it : tmpList)
   			{
   				//++counter - to bylo kiedys ale teraz iteruje w ifie
   				if (++counter % 2 == 0)
   					whiteMove = it;
   				else if (counter % 3 == 0)
   				{
   					blackMove = it;
   					Move m = new Move(whiteMove, blackMove);
   					game.add(m);
   					counter = 0;
   				}
   			}
		}
		  
		private Logger logger = Logger.getLogger("LOG");
		private String path = null;
	}
}
